﻿using System;
using System.Collections.Generic;
using Bling.DSL;
using Bling.Util;
using Bling.Linq;
using Bling.Core;
using Bling.Matrices;
using Bling.Vecs;

namespace Bling.Core {
  public interface IConditionOp { }
  public abstract partial class Brand<T, BRAND> : Brand<BRAND>, IBrandT<T>, IExtends<BRAND, ObjectBl>, IBrand<T, BRAND> where BRAND : Brand<T, BRAND> {
    internal static Func<Expr<T>, Expr<T>, Expr<T>> MinE;
    internal static Func<Expr<T>, Expr<T>, Expr<T>> MaxE;

    public partial class ConditionAOp : TrinaryOperator<bool, T, T, T, ConditionAOp>, IConditionOp {
      public override Expr<T> Derivative(Expr<bool> argA, Expr<T> argB, Expr<T> argC, Dictionary<Expr, Expr> Map) {
        var DargB = argB.Derivative(Map);
        var DargC = argC.Derivative(Map);
        if (DargB == null || DargC == null) return null;
        return Instance.Make(argA, DargB, DargC);
      }
      public override Expr<T> Extent(Expr<bool> argA, Expr<T> argB, Expr<T> argC, bool MaxD) {
        var a = argB.Extent(MaxD);
        var b = argC.Extent(MaxD);
        if (MaxD && MaxE != null) return MaxE(a, b);
        else if (!MaxD && MinE != null) return MinE(a, b);
        else return base.Extent(argA, argB, argC, MaxD);
      }
    }
  }
}

namespace Bling.Core {
  public abstract partial class BaseNumericBrand<T, BRAND, K, D, SCALAR> : ValueBrand<T, BRAND, K, D, SCALAR>, INumeric<BRAND, D, SCALAR>
    where BRAND : NumericBrand<T, BRAND, K, D, SCALAR>
    where D : Dim<D>
    where SCALAR : NumericBrand<K, SCALAR, K, D1, SCALAR> {

    private static readonly int ConfigureDerivative0 = ConfigureDerivative();
    private static int ConfigureDerivative() {
      MaxE = (a, b) => ToBrand(a).Max(ToBrand(b));
      MinE = (a, b) => ToBrand(a).Min(ToBrand(b));
      return 0;
    }

    public partial class AddOp : BinaryOp<AddOp> {
      public override Expr<IVec<K, D>> Derivative(Expr<IVec<K, D>> argA, Expr<IVec<K, D>> argB, Dictionary<Expr, Expr> Map) {
        var DargA = argA.Derivative(Map);
        var DargB = argB.Derivative(Map);
        if (DargA == null || DargB == null) return base.Derivative(argA, argB, Map);
        return Instance.Make(DargA, DargB);
      }
      public override Expr<IVec<K, D>> Extent(Expr<IVec<K, D>> argA, Expr<IVec<K, D>> argB, bool Max) {
        var a = argA.Extent(Max);
        var b = argB.Extent(Max);
        return Instance.Make(a, b);
      }
    }
    public partial class SubtractOp : BinaryOp<SubtractOp> {
      public override Expr<IVec<K, D>> Derivative(Expr<IVec<K, D>> argA, Expr<IVec<K, D>> argB, Dictionary<Expr, Expr> Map) {
        var DargA = argA.Derivative(Map);
        var DargB = argB.Derivative(Map);
        if (DargA == null || DargB == null) return base.Derivative(argA, argB, Map);
        return Instance.Make(DargA, DargB);
      }
      public override Expr<IVec<K, D>> Extent(Expr<IVec<K, D>> argA, Expr<IVec<K, D>> argB, bool Max) {
        var a = argA.Extent(Max);
        var b = ToBrandD(argB).Negate.Provider.Extent(Max);
        return ToBrandD(a) + ToBrand(b);
      }
    }
    public partial class MultiplyOp : BinaryOp<MultiplyOp> {
      public override Expr<IVec<K, D>> Derivative(Expr<IVec<K, D>> argA, Expr<IVec<K, D>> argB, Dictionary<Expr, Expr> Map) {
        var DargA = argA.Derivative(Map);
        var DargB = argB.Derivative(Map);
        if (DargA == null || DargB == null) return base.Derivative(argA, argB, Map);
        return (ToBrandD(argA) * ToBrandD(DargB)) + (ToBrandD(argB) * ToBrandD(DargA));
      }
      public override Expr<IVec<K, D>> Extent(Expr<IVec<K, D>> argA, Expr<IVec<K, D>> argB, bool MaxD) {
        var a0 = argA.Extent(MaxD);
        var b0 = argB.Extent(MaxD);
        return Instance.Make(a0, b0);
      }
    }
    public partial class DivideOp : BinaryOp<DivideOp> {
      public override Expr<IVec<K, D>> Derivative(Expr<IVec<K, D>> argA, Expr<IVec<K, D>> argB, Dictionary<Expr, Expr> Map) {
        if (typeof(K) == typeof(double)) {
          // use multiplication
          var ArgA = FlexDoubleBl<D>.ToBrand((Expr<IVec<double, D>>)(object)argA);
          var ArgB = FlexDoubleBl<D>.ToBrand((Expr<IVec<double, D>>)(object)argB);
          var Result = (ArgA * ArgB.Recip).UnderlyingD.Derivative(Map);
          return (Expr<IVec<K, D>>)(object)Result;
        } else return base.Derivative(argA, argB, Map);
      }
      public override Expr<IVec<K, D>> Extent(Expr<IVec<K, D>> argA, Expr<IVec<K, D>> argB, bool MaxD) {
        if (MaxD) {
          var aH = argA.Extent(MaxD);
          var bL = argB.Extent(!MaxD);
          return Instance.Make(aH, bL);
        } else {
          var aL = argA.Extent(!MaxD);
          var bH = argB.Extent(MaxD);
          return Instance.Make(aL, bH);
        }
      }
    }

    public partial class AbsOp : CallUnaryOp<AbsOp>, IMathLinqOperator {
      public override Expr<IVec<K, D>> Derivative(Expr<IVec<K, D>> argA, Dictionary<Expr, Expr> Map) {
        var DArgA = argA.Derivative(Map);
        if (DArgA == null) return base.Derivative(argA, Map);
        return (ToBrandD(argA) < Repeat(default(K))).Condition(
          ToBrand(ToBrandD(argA).Negate.Provider.Derivative(Map)), 
          ToBrandD(DArgA));
      }
      public override Expr<IVec<K, D>> Extent(Expr<IVec<K, D>> argA, bool MaxD) {
        var a0 = ToBrandD(argA.Extent(MaxD)).Abs;
        var a1 = ToBrandD(argA.Extent(!MaxD)).Abs;
        if (MaxD) return a0.Max(a1); else return a0.Min(a1);
      }
    }
    public partial class MinOp : CallBinaryOp<MinOp>, IMathLinqOperator {
      public override Expr<IVec<K, D>> Derivative(Expr<IVec<K, D>> argA, Expr<IVec<K, D>> argB, Dictionary<Expr, Expr> Map) {
        var DArgA = argA.Derivative(Map);
        var DArgB = argB.Derivative(Map);
        if (DArgA == null || DArgB == null)
          return base.Derivative(argA, argB, Map);
        else
          return (ToBrandD(argA) < ToBrandD(argB)).Condition(ToBrandD(DArgA), ToBrandD(DArgB));
      }
      public override Expr<IVec<K, D>> Extent(Expr<IVec<K, D>> argA, Expr<IVec<K, D>> argB, bool MaxD) {
        var a = argA.Extent(MaxD);
        var b = argB.Extent(MaxD);
        if (MaxD)
          return ToBrandD(a).Max(ToBrandD(b));
        else return ToBrandD(a).Min(ToBrandD(b));
      }
    }
    public partial class MaxOp : CallBinaryOp<MaxOp>, IMathLinqOperator {
      public override Expr<IVec<K, D>> Derivative(Expr<IVec<K, D>> argA, Expr<IVec<K, D>> argB, Dictionary<Expr, Expr> Map) {
        var DArgA = argA.Derivative(Map);
        var DArgB = argB.Derivative(Map);
        if (DArgA == null || DArgB == null) 
          return base.Derivative(argA, argB, Map);
        else 
          return (ToBrandD(argA) > ToBrandD(argB)).Condition(ToBrandD(DArgA), ToBrandD(DArgB));
      }
      public override Expr<IVec<K, D>> Extent(Expr<IVec<K, D>> argA, Expr<IVec<K, D>> argB, bool MaxD) {
        var a = argA.Extent(MaxD);
        var b = argB.Extent(MaxD);
        if (MaxD)
          return ToBrandD(a).Max(ToBrandD(b));
        else return ToBrandD(a).Min(ToBrandD(b));
      }
    }
    public partial class NegateOp : UnaryOp<NegateOp> {
      public override Expr<IVec<K, D>> Derivative(Expr<IVec<K, D>> argA, Dictionary<Expr, Expr> Map) {
        var DargA = argA.Derivative(Map);
        if (DargA != null)
          return Instance.Make(DargA);
        return base.Derivative(argA, Map);
      }
      public override Expr<IVec<K, D>> Extent(Expr<IVec<K, D>> argA, bool Max) {
        var a = argA.Extent(!Max);
        return Instance.Make(a);
      }
    }
  }
  public abstract partial class DoubleBl<T, BRAND, D> : NumericBrand<T, BRAND, double, D, DoubleBl>, IHasX<double,DoubleBl>, IDouble<BRAND,D>, IDouble2<BRAND>
    where BRAND : DoubleBl<T, BRAND, D>
    where D : Dim<D> {
    public partial class LengthOp : UnaryOperatorS<double, LengthOp>, INoLinqOperator {
      public override Expr<double> Extent(Expr<IVec<double, D>> argA, bool Max) {
        return Instance.Make(argA.Extent(Max));
      }
    }
    public partial class RecipOp : CallUnaryOp<RecipOp>, INoLinqOperator {
      public override Expr<IVec<double, D>> Derivative(Expr<IVec<double, D>> argA, Dictionary<Expr, Expr> Map) {
        return ToBrandD(argA).Pow(Repeat(-1d)).UnderlyingD.Derivative(Map);
      }
      public override Expr<IVec<double, D>> Extent(Expr<IVec<double, D>> argA, bool Max) {
        return Instance.Make(argA.Extent(!Max));
      }
    }
    public partial class ExpOp : CallUnaryOp<ExpOp>, IMathLinqOperator {
      public override Expr<IVec<double, D>> Derivative(Expr<IVec<double, D>> argA, Dictionary<Expr, Expr> Map) {
        var DargA = argA.Derivative(Map);
        if (DargA == null) return base.Derivative(argA, Map);
        return ToBrandD(DargA) * ToBrandD(argA).Exp;
      }
      public override Expr<IVec<double, D>> Extent(Expr<IVec<double, D>> argA, bool Max) {
        return Instance.Make(argA.Extent(Max));
      }
    }
    public partial class LogOp : CallUnaryOp<LogOp>, IMathLinqOperator {
      public override Expr<IVec<double, D>> Derivative(Expr<IVec<double, D>> argA, Dictionary<Expr, Expr> Map) {
        var DargA = argA.Derivative(Map);
        if (DargA == null) return base.Derivative(argA, Map);
        return ToBrandD(DargA) / ToBrandD(argA);
      }
      public override Expr<IVec<double, D>> Extent(Expr<IVec<double, D>> argA, bool Max) {
        return Instance.Make(argA.Extent(Max));
      }
    }
    public partial class PowOp : CallBinaryOp<PowOp>, IMathLinqOperator {
      public override Expr<IVec<double, D>> Derivative(Expr<IVec<double, D>> argA, Expr<IVec<double, D>> argB, Dictionary<Expr, Expr> Map) {
        var a = argA.Fold;
        var r = argB.Fold;
        if (a.IsConstant) {
          // a^x 
          return ToBrandD(a).Log * ToBrandD(a).Pow(ToBrandD(r));
        } else if (r.IsConstant) {
          return ToBrandD(r) * ToBrandD(a).Pow(ToBrandD(r) - Repeat(1d));
        }
        return base.Derivative(argA, argB, Map);
      }
      public override Expr<IVec<double, D>> Extent(Expr<IVec<double, D>> argA, Expr<IVec<double, D>> argB, bool Max) {
        var a = argA.Extent(Max);
        var r = argB.Extent(Max);
        return Instance.Make(a, r);
      }
    }
    public partial class SqrtOp : CallUnaryOp<SqrtOp>, IMathLinqOperator {
      public override Expr<IVec<double, D>> Derivative(Expr<IVec<double, D>> argA, Dictionary<Expr, Expr> Map) {
        return ToBrandD(argA).Pow(Repeat(.5)).UnderlyingD.Derivative(Map);
      }
      public override Expr<IVec<double, D>> Extent(Expr<IVec<double, D>> argA, bool Max) {
        return Instance.Make(argA.Extent(Max));
      }
    }
  }
  public abstract partial class RealDoubleBl<T, BRAND, D> : DoubleBl<T, BRAND, D>
    where BRAND : RealDoubleBl<T, BRAND, D>
    where D : Dim<D> {
    public partial class AsinOp : CallUnaryOp<AsinOp>, IMathLinqOperator {
      public override Expr<IVec<double, D>> Derivative(Expr<IVec<double, D>> argA, Dictionary<Expr, Expr> Map) {
        var DargA = argA.Derivative(Map);
        if (DargA == null) return base.Derivative(argA, Map);
        return ToBrandD(DargA) / (Repeat(1d) - ToBrandD(argA).Square).Sqrt;
      }
    }
    public partial class AcosOp : CallUnaryOp<AcosOp>, IMathLinqOperator {
      public override Expr<IVec<double, D>> Derivative(Expr<IVec<double, D>> argA, Dictionary<Expr, Expr> Map) {
        var DargA = argA.Derivative(Map);
        if (DargA == null) return base.Derivative(argA, Map);
        return -ToBrandD(DargA);
      }
    }
  }
}

namespace Bling.Angles {
  using Bling.Core;

  public abstract partial class RadianBl<T, BRAND, D> : Angle2DBl<T, BRAND, D, RadianBl>, ICanInterpolate<BRAND>
    where BRAND : RadianBl<T, BRAND, D>
    where D : Dim<D> {
    public partial class SinOp : CallUnaryOp<SinOp>, IMathLinqOperator {
      public override Expr<IVec<double, D>> Derivative(Expr<IVec<double, D>> argA, Dictionary<Expr, Expr> Map) {
        var DargA = argA.Derivative(Map);
        if (DargA == null) return base.Derivative(argA, Map);
        return ToBrandD(DargA) * ToBrandD(argA).Cos;
      }
    }
    public partial class CosOp : CallUnaryOp<CosOp>, IMathLinqOperator {
      public override Expr<IVec<double, D>> Derivative(Expr<IVec<double, D>> argA, Dictionary<Expr, Expr> Map) {
        var DargA = argA.Derivative(Map);
        if (DargA == null) return base.Derivative(argA, Map);
        return ToBrandD(DargA) * (-ToBrandD(argA).Sin);
      }
    }
  }
}

namespace Bling.Vecs {
  using Bling.Core;
  using Bling.Ops;
  public partial class Composite<T, D> : Expr<IVec<T, D>> where D : Dim<D> {
    public override Expr<IVec<T, D>> Extent(bool Max) {
      var Ek = new Expr<T>[Dim<D>.Count];
      for (int i = 0; i < Dim<D>.Count; i++)
        Ek[i] = this[i].Extent(Max);
      return Make(Ek);
    }
    protected override Expr<IVec<T, D>> Derivative1(Dictionary<Expr, Expr> Map) {
      var Ek = new Expr<T>[Dim<D>.Count];
      for (int i = 0; i < Dim<D>.Count; i++) {
        Ek[i] = this[i].Derivative(Map);
        if (Ek[i] == null) 
          return base.Derivative1(Map);
      }
      return Make(Ek);
    }
  }
  public partial class AccessExpr<T, D> : BaseAccessExpr<T, D> where D : Dim<D> {
    public override Expr<T> Extent(bool Max) {
      return Make(Vector.Extent(Max), Index);
    }
    protected override Expr<T> Derivative1(Dictionary<Expr, Expr> Map) {
      var Vector = this.Vector;
      var DVector = Vector.Derivative(Map);
      if (DVector != null)
        return Make(DVector, Index);

      //return new AccessExpr(DVector, Idx);

      /*
      Expr<IVec<T,D>> expanded = null;
      Operation<IVec<T,D>> lastExpanded = null;
      (!(Vector is Composite<T,D>)).Assert();
      expanded = Vector;
      while (true) {
        if (expanded is Operation<IVec<T,D>>) {
          lastExpanded = (Operation<IVec<T,D>>)expanded;
          if (((Operation<IVec<T,D>>)expanded).BaseOp is IConditionOp) {
            var expanded0 = (Operation<bool, IVec<T, D>, IVec<T, D>, IVec<T, D>>)expanded;
            if (expanded0.ArgB is Composite<T,D> && expanded0.ArgC is Composite<T,D>) {


              var ret = ValueOperators<K>.Condition.Instance.Make(expanded0.ArgA,
                ((VecExpr)expanded0.ArgB)[Idx],
                ((VecExpr)expanded0.ArgC)[Idx]).Derivative(Map);
              if (ret != null) return ret;
              else expanded = null;
            } else if (expanded0.ArgB is Operation<T> && expanded0.ArgC is Operation<T>) {
              var argB = ((Operation<T>)expanded0.ArgB).Expanded;
              var argC = ((Operation<T>)expanded0.ArgC).Expanded;
              if (argB != null && argC != null)
                expanded = ValueOperators<T>.Condition.Instance.Make(expanded0.ArgA, argB, argC);
              else
                expanded = null;
            } else
              expanded = null;
          } else {
            expanded = ((Operation<T>)expanded).Expanded;
            if (expanded is VecExpr) {
              var e = ((VecExpr)expanded)[Idx];
              var ret = e.Derivative(Map);
              if (ret != null) return ret;
              else expanded = null;
            } else {
              true.Assert();
            }
          }
        } else if (expanded is Constant<T>) return new Constant<K>(default(K));
        else if (expanded is ProxyExpr<T>) expanded = ((ProxyExpr<T>)expanded).Underlying;
        else break;
      }*/
      return base.Derivative1(Map);
    }
  }
  public partial class Access2DExpr<T, D> : BaseAccessExpr<T, D>
    where D : Dim2D<D> {
    protected override Expr<T> Derivative1(Dictionary<Expr, Expr> Map) {
      var Vector = this.Vector;
      var DVector = Vector.Derivative(Map);
      if (DVector != null)
        return Make(DVector, IndexRow, IndexCol);
      return base.Derivative1(Map);
    }
  }

}